package com.itheima.netty.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;

import static com.itheima.utils.ByteBufferUtil.debugAll;
import static com.itheima.utils.ByteBufferUtil.debugRead;

/**
 * \* Created with IntelliJ IDEA.
 * \* User: pzj
 * \* Date: 2021/11/10
 * \* Time: 11:22
 * \* Description:  NIO演示非阻塞模式
 * \
 */
@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //1、创建 selector  可以管理多个channel
        final Selector selector = Selector.open();

        final ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        //2、建立  selector    和   channel 的联系（将channel注册到selector上）
        //SelectionKey  事件发生后，可以通过它知道是哪个channel发生的事件
        final SelectionKey selectionKey = ssc.register(selector, 0, null);
        //指明这个key只关注accept事件
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("register key:{}",selectionKey);
        ssc.bind(new InetSocketAddress(8080));
        while (true){
            //3、select方法,没有事件发生，线程阻塞，有事件发生，线程恢复运行
            //select 在事件没有处理时，它就不会阻塞，所以，事件发生后要么处理要么取消，不能不响应
            selector.select();
            //4、处理事件，selectionKeys内部包含了所有发生的事件
            final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                final SelectionKey key = iterator.next();
                iterator.remove();  //获取key之后，将它从selectedKeys集合中移除，否则，事件处理完，继续遍历selectedKeys，会报错
                log.debug("key:{}",key);
                //5、区分事件类型
                if (key.isAcceptable()){    //accept事件处理逻辑
                    //通过key获取对应的channel
                    final ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    //处理key上的accept事件
                    final SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    final ByteBuffer buffer = ByteBuffer.allocate(16);  //attachment  附件注册到selector上  这样，channel和buffer就关联起来了
                    //建立channel连接后，注册到selector上
                    final SelectionKey scKey = sc.register(selector, 0, buffer);
                    //设置关注的事件
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}",sc);
                }else if (key.isReadable()){    //read事件
                    //客户端正常断开：close，或者是强制断开：宕机。都会被视为 read 事件
                    try {
                        final SocketChannel channel = (SocketChannel) key.channel();
                        //获取我们关联的buffer
                        final ByteBuffer buffer = (ByteBuffer) key.attachment();
                        final int read = channel.read(buffer);  //如果正常断开，read的方法返回值是-1
                        if ( read != -1){
//                            buffer.flip();
//                            debugRead(buffer);
                            split(buffer);
                            if (buffer.position() == buffer.limit()){   //如果相等，说明buffer容量被填满，则需要扩容
                                final ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
                                buffer.flip();//切换读模式，因为split中最后切换成写模式
                                newBuffer.put(buffer);  //拷贝原先buffer中的内容到新的buffer中
                                key.attach(newBuffer);  //把新的扩容后的buffer加入key中
                            }
                        }else {
                            //处理客户调用close正常断开的情况
                            key.cancel();
                        }
                    } catch (IOException e) {
                        //处理客户端异常断开的情况
                        e.printStackTrace();
                        key.cancel();   //因为客户端断开了，因此需要将 key 取消（从 selectedKeys 集合中真正删除）
                    }
                }

                //取消key上放生的时间
//                key.cancel();
            }
        }

    }

    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            //找到一条完整的消息
            if (source.get(i) == '\n') {
                int len = i+1 - source.position();
                //把这条完整消息存入新的 ByteBuffer
                ByteBuffer target = ByteBuffer.allocate(len);
                //从source读，向target写
                for (int j = 0; j < len; j++) {
                    target.put(source.get());
                }
                debugAll(target);
            }
        }
        source.compact();
    }
}